
#include "command2.hpp"
#include "util.hpp"

char* cmd2str(cmd_type t, char* str);
match_type  cmd_ipv4_match (const std::string str);
match_type  cmd_match(std::string src, std::string dst);

template <typename Function> 
bool cmd_is_range(const std::string& str, int min, int max, Function function=isdigit) 
{
    int base=10;
    PDBG("check: %s, min: %d, max: %d\n", str.c_str(), min, max);
    for (int idx =0; idx < str.length(); idx++) {
        //if (!isdigit(str[idx])) return false;
        if (!function(str[idx])) return false;
    }
    if (function == isdigit) {   /* FIXME */
        base = 10;
    } else if (function == isxdigit) {
        base = 16;
    }
    
    int num = strtol(str.c_str(), NULL, base);
    PDBG("check: %d, min: %d, max: %d\n", num, min, max);
    if (num >= min && num <= max) return true;
    return false;
}

cmd_type get_cmd_type(const std::string& cmd);

command2::command2():
    _type(ROOT_TYPE), _cmd("__ROOT__"), _help("ROOT NODE\n")
    , _min(0), _max(0)
{
}

command2::command2(std::string cmd, std::string help, ExcuteHandle function):
    _cmd(cmd), _help(help), _function(function) , _min(0), _max(0)
{
    char tmp[256];
    _type = get_cmd_type(_cmd);
    if (_type == INT_RANGE_TYPE) {
        sscanf(_cmd.c_str(), "<%d-%d>", &_min, &_max);
    } else if (_type == HEX_RANGE_TYPE) {
        sscanf(_cmd.c_str(), "<%x-%x>", &_min, &_max);
    }

    PDBG("crt, p: %p t: %s, c: %s h: %s min:%d max:%d\n", 
            this, cmd2str(_type, tmp), _cmd.c_str(), _help.c_str(),
            _min, _max);
}

command2::~command2() 
{
#if 0
    char tmp[256];
    PDBG("del, p: %p T: %s, C: %s H: %s\n", 
            this, cmd2str(_type, tmp), _cmd.c_str(), _help.c_str());
#endif
}

match_type command2::mathed(const std::string& cmdstr)
{
    match_type ret = no_match;
    switch(_type) {
    case IPV4_ADDR_TYPE:
        ret = cmd_ipv4_match(cmdstr);
        break;
    case IPV4_PREFIX_TYPE:
        ret = no_match;
        break;
    case HEX_RANGE_TYPE:
    {
        if (cmdstr.compare(0,2,"0x") != 0) return no_match;
        std::string tmp(cmdstr.begin()+2, cmdstr.end());
        if (cmd_is_range(tmp, _min, _max, isxdigit) == true) 
            ret = exact_match;
        break;
    }
    case INT_RANGE_TYPE:
        if (cmd_is_range(cmdstr, _min, _max, isdigit) == true) 
            ret = exact_match;
        break;
    case STRING_TYPE:
        if (cmdstr.length() > 0) 
            ret = partly_match;
        break;
    case CMD_TYPE:
    case OPT_TYPE:
        ret = cmd_match(_cmd, cmdstr);
        break;
    default:
        break;
    }

    /*
    char buf[256];
    PDBG("RET: %d , C: %s T: %s, src: %s\n", ret, 
            _cmd.c_str(), cmd2str(_type, buf), cmdstr.c_str()); */
    return ret;
}

int command2::find_children(const std::string& str, cmd_list_t& cmdlist)
{
    int num=0;
    match_type t;
    cmd_list_iterator iter = _childs.begin();
    for (; iter != _childs.end(); iter++) {
        t = (*iter)->mathed(str);
        if(t == exact_match || t == partly_match) {
            PDBG(">> ME(%s) CHD(%s), TRG(%s) CHD_TYPE(%d)\n", 
                    _cmd.c_str(), (*iter)->get_cmd().c_str(), str.c_str(), (*iter)->get_type());
            cmdlist.push_back(*iter);
            num++;
        }
    }
    return num;
};

int command2::find_children(match_type type, const std::string& str, cmd_list_t& cmdlist)
{
    int num=0;
    cmd_list_iterator iter = _childs.begin();
    for (; iter != _childs.end(); iter++) {
        if((*iter)->mathed(str) == type) {
            PDBG(">> ME(%s) CHD(%s), TRG(%s) CHD_TYPE(%d)\n", 
                    _cmd.c_str(), (*iter)->get_cmd().c_str(), str.c_str(), (*iter)->get_type());
            cmdlist.push_back(*iter);
            num++;
        }
    }
    return num;
};




match_type  cmd_match(std::string src, std::string dst)
{
    match_type ret = no_match;
    if (src.compare(dst) == 0) return exact_match;
    else if (src.length() > dst.length()) {
        if (src.compare(0, dst.length(), dst) == 0)
            return partly_match;
    } else if (dst.length() > src.length()) {
        if (dst.compare(0, src.length(), src) == 0)
            return extend_match;
    } 
    return ret;
}

match_type cmd_ipv4_match (const std::string str)
{
    char dot = '.';
    size_t p_pos = 0, dot_pos = 0, dot_num = 0, size = str.length();
    string_vector   vec;
    std::string num;

    //PDBG("size: %d\n", size);
    if (size == 0) return no_match;
    if (size >  15) return no_match;
    if (size < 4) {
        if (cmd_is_range(str, 0, 255, isdigit)) return partly_match;
        return no_match;
    }

    while (1) {
        p_pos = dot_pos;
        dot_pos = str.find(dot, p_pos);
        if (dot_pos == std::string::npos) 
            break;
        dot_num++;
        if (dot_num > 3) return no_match;
        num = str.substr(p_pos, (dot_pos-p_pos));
        //PDBG("num: %s\n", num.c_str());
        if (cmd_is_range(num, 0, 255, isdigit) != true) return no_match;
        dot_pos++;
    }
    //PDBG("size: %d dot_num:%d\n", size, p_pos);
    if (size == p_pos) return partly_match;
    num = str.substr(p_pos);
    if (cmd_is_range(num, 0, 255, isdigit) != true) return no_match;
    if (dot_num == 3) return exact_match;
    return partly_match;
}


cmd_type get_cmd_type(const std::string& cmd)
{
    if (is_ipv4(cmd)) return IPV4_ADDR_TYPE;
    else if (is_ipv4_prefix(cmd)) return IPV4_PREFIX_TYPE;
    else if (is_hex_range(cmd)) return HEX_RANGE_TYPE;  /* must pre int range */
    else if (is_int_range(cmd)) return INT_RANGE_TYPE;
    else if (is_string(cmd)) return STRING_TYPE;
    return CMD_TYPE;
}

char* cmd2str(cmd_type t, char* str) 
{
    if (t == CMD_TYPE) sprintf(str, "CMD_TYPE");
    else if (t == IPV4_ADDR_TYPE) sprintf(str, "IPV4");
    else if (t == IPV4_PREFIX_TYPE) sprintf(str, "IPV4_PREF");
    else if (t == IPV4_PREFIX_TYPE) sprintf(str, "IPV4_PREF");
    else if (t == INT_RANGE_TYPE) sprintf(str, "INT_RANGE_TYPE");
    else if (t == HEX_RANGE_TYPE) sprintf(str, "HEX_RANGE_TYPE");
    else if (t == OPT_TYPE) sprintf(str, "OPT_TYPE");
    else if (t == STRING_TYPE) sprintf(str, "STRING_TYPE");
    else if (t == MULTI_OPT_TYPE) sprintf(str, "MULTI OPT");
    else if (t == CR_TYPE) sprintf(str, "CR OPT");
    else if (t == ROOT_TYPE) sprintf(str, "CR OPT");
    else {
        sprintf(str, "UNKOWN TYPE");
        PDBG("(%d) UNKOWN TYPE", t);
    }
    return str;
}

